package com.example.compose

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.calculateTargetValue
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.splineBasedDecay
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.awaitDragOrCancellation
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitHorizontalDragOrCancellation
import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation
import androidx.compose.foundation.gestures.horizontalDrag
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathMeasure
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.asComposePath
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import androidx.graphics.shapes.CornerRounding
import androidx.graphics.shapes.Morph
import androidx.graphics.shapes.RoundedPolygon
import androidx.graphics.shapes.circle
import androidx.graphics.shapes.star
import androidx.graphics.shapes.toPath
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.math.absoluteValue
import kotlin.math.roundToInt


@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
private fun AnimatedVisibilityPreview() {

    val coroutineScope = rememberCoroutineScope()

    var show by remember {
        mutableStateOf(false)
    }
    val backgroundColor by animateColorAsState(
        targetValue = if (show) Color.Cyan else Color.Gray,
        label = "background color"
    )

    suspend fun showEditMessage() {
        if (!show) {
            show = true
            delay(3000L)
            show = false
        }
    }

    Scaffold(
        modifier = Modifier.swipeToXX(),
        containerColor = backgroundColor,
        topBar = {
            MediumTopAppBar(title = { Text(text = "标题栏") }, modifier = Modifier.shadow(4.dp))
        },
        floatingActionButton = {
            SmallFloatingActionButton(
                onClick = {
                    coroutineScope.launch {
                        showEditMessage()
                    }
                }
            ) {
                Text(text = "弹出新消息")
            }
        }
    ) { paddingValue ->
        Column(modifier = Modifier.padding(paddingValue)) {
            AnimatedVisibility(
                visible = show,
                enter = slideInVertically(initialOffsetY = { fullHeight -> -fullHeight }),
                exit = slideOutVertically(targetOffsetY = { fullHeight -> -fullHeight })
            ) {
                Surface(
                    modifier = Modifier.fillMaxWidth(),
                    shadowElevation = 4.dp,
                    color = MaterialTheme.colorScheme.secondary
                ) {
                    Text(text = "新消息", modifier = Modifier.padding(16.dp))
                }
            }
        }
    }

}

private fun Modifier.swipeToXX(): Modifier = composed {
    val offsetX = remember {
        Animatable(0f)
    }
    pointerInput(Unit) {
        val decay = splineBasedDecay<Float>(this)
        coroutineScope {
            while (true) {
                val pointerId = awaitPointerEventScope { awaitFirstDown().id }
                offsetX.stop()
                val velocityTracker = VelocityTracker()
                awaitPointerEventScope {
                    horizontalDrag(pointerId) { change ->
                        val horizontalDragOffset = offsetX.value + change.positionChange().x
                        launch {
                            offsetX.snapTo(horizontalDragOffset)
                        }
                        velocityTracker.addPosition(change.uptimeMillis, change.position)
                        change.consume()
                    }
                }
                val velocity = velocityTracker.calculateVelocity().x
                val targetOffsetX = decay.calculateTargetValue(offsetX.value, velocity)
                offsetX.updateBounds(
                    lowerBound = -size.width.toFloat(),
                    upperBound = size.width.toFloat()
                )
                launch {
                    if (targetOffsetX.absoluteValue <= size.width) {
                        offsetX.animateTo(targetValue = 0f, initialVelocity = velocity)
                    } else {
                        offsetX.animateDecay(velocity, decay)
                    }
                }
            }
        }
    }
        .offset { IntOffset(offsetX.value.roundToInt(), 0) }
}

// 等待拖动
@Preview
@Composable
private fun AwaitDragPreview() {
    val offsetX = remember {
        mutableFloatStateOf(0f)
    }
    val offsetY = remember {
        mutableFloatStateOf(0f)
    }
    var size by remember {
        mutableStateOf(Size.Zero)
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .onSizeChanged { size = it.toSize() }
    ) {
        Box(
            modifier = Modifier
                .offset {
                    IntOffset(
                        offsetX.floatValue.roundToInt(),
                        offsetY.floatValue.roundToInt()
                    )
                }
                .size(50.dp)
                .background(Color.Blue)
                .pointerInput(Unit) {
                    awaitEachGesture {
                        val down = awaitFirstDown()
                        var change = awaitTouchSlopOrCancellation(down.id) { change, overSlop ->
                            val original = Offset(offsetX.floatValue, offsetY.floatValue)
                            val summed = original + overSlop
                            val newValue = Offset(
                                x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
                                y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
                            )
                            change.consume()
                            offsetX.floatValue = newValue.x
                            offsetY.floatValue = newValue.y
                        }
                        while (change != null && change.pressed) {
                            change = awaitDragOrCancellation(change.id)
                            if (change != null && change.pressed) {
                                val original = Offset(offsetX.floatValue, offsetY.floatValue)
                                val summed = original + change.positionChange()
                                val newValue = Offset(
                                    x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
                                    y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
                                )
                                change.consume()
                                offsetX.floatValue = newValue.x
                                offsetY.floatValue = newValue.y
                            }
                        }
                    }
                }
        )
    }
}


//水平拖动
@Preview
@Composable
private fun HorizontalDrag() {
    val offsetX = remember {
        mutableFloatStateOf(0f)
    }
    val offsetY = remember {
        mutableFloatStateOf(0f)
    }
    var width by remember {
        mutableFloatStateOf(0f)
    }

    Box(modifier = Modifier
        .fillMaxSize()
        .onSizeChanged { width = it.width.toFloat() }
    ) {
        Box(
            modifier = Modifier
                .offset {
                    IntOffset(
                        offsetX.floatValue.roundToInt(),
                        offsetY.floatValue.roundToInt()
                    )
                }
                .size(50.dp)
                .background(color = Color.Blue)
                .pointerInput(Unit) {
                    awaitEachGesture {
                        val down = awaitFirstDown()
                        var change =
                            awaitHorizontalTouchSlopOrCancellation(down.id) { change, overSlop ->
                                val originalX = offsetX.floatValue
                                val newValue =
                                    (originalX + overSlop).coerceIn(0f, width - 50.dp.toPx())
                                change.consume()
                                offsetX.floatValue = newValue
                            }
                        while (change != null && change.pressed) {
                            change = awaitHorizontalDragOrCancellation(change.id)
                            if (change != null && change.pressed) {
                                val originalX = offsetX.floatValue
                                val newValue = (originalX + change.positionChange().x)
                                    .coerceIn(0f, width - 50.dp.toPx())
                                change.consume()
                                offsetX.floatValue = newValue
                            }
                        }
                    }
                }
        )
    }
}


@Preview
@Composable
fun HeartReteAnimation() {
    val boxState by remember {
        mutableStateOf(BoxState.Collapsed)
    }
    var isHeartBeating by remember {
        mutableStateOf(false)
    }
    val animatedValue by animateIntAsState(targetValue = if (isHeartBeating) 255 else 0, label = "")
    val alpha by animateFloatAsState(targetValue = if (isHeartBeating) 1f else 0f, label = "")
    val transition = updateTransition(targetState = boxState, label = "boxState Transition")
    val scale by transition.animateFloat(
        transitionSpec = { spring(stiffness = 50f) },
        label = ""
    ) { state ->
        if (state == BoxState.Expanded) 1f else 0.5f
    }
    val heartScale by animateFloatAsState(
        targetValue = if (isHeartBeating) 1f else 0.2f,
        label = ""
    )
    val heartColor by animateColorAsState(
        targetValue = if (isHeartBeating) Color.Red else Color.Gray,
        label = ""
    )
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
        modifier = Modifier.fillMaxSize()
    ) {
        Icon(
            painter = painterResource(id = R.drawable.heart),
            contentDescription = "heart",
            tint = heartColor,
            modifier = Modifier
                .scale(heartScale)
                .clickable {
                    isHeartBeating = !isHeartBeating
                }
        )
    }


}

enum class BoxState {
    Expanded,
    Collapsed
}


@Preview
@Composable
private fun PolygonPreview() {

    val infiniteTransition = rememberInfiniteTransition(label = "infinite")
    val progress = infiniteTransition.animateFloat(
        initialValue = 0f,
        targetValue = 1f,
        animationSpec = infiniteRepeatable(
            tween(durationMillis = 4000, easing = LinearEasing),
            repeatMode = RepeatMode.Reverse
        ),
        label = "progress"
    )
    val rotation = infiniteTransition.animateFloat(
        initialValue = 0f,
        targetValue = 360f,
        animationSpec = infiniteRepeatable(
            tween(durationMillis = 4000, easing = LinearEasing),
            repeatMode = RepeatMode.Reverse
        ),
        label = "rotation"
    )

    val starPolygon = remember {
        RoundedPolygon.star(
            numVerticesPerRadius = 12,
            innerRadius = 1f / 3f,
            rounding = CornerRounding(radius = 1f / 6f)
        )
    }
    val circlePolygon = remember {
        RoundedPolygon.circle(
            numVertices = 12
        )
    }
    val morph = remember {
        Morph(start = starPolygon, end = circlePolygon)
    }

    var morphPath: Path
    var androidPath = remember {
        android.graphics.Path()
    }
    val matrix = remember {
        Matrix()
    }

    val pathMeasurer = remember {
        PathMeasure()
    }
    var destinationPath = remember {
        Path()
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
            .drawWithCache {
                androidPath = morph.toPath(progress = progress.value, androidPath)
                morphPath = androidPath.asComposePath()
                matrix.reset()
                matrix.scale(size.minDimension / 2f, size.minDimension / 2f)
                morphPath.transform(matrix = matrix)

                pathMeasurer.setPath(path = morphPath, forceClosed = false)
                val totalLength = pathMeasurer.length
                destinationPath.reset()
                pathMeasurer.getSegment(0f, totalLength * progress.value, destinationPath)

                onDrawBehind {
                    rotate(degrees = rotation.value) {
                        translate(size.width / 2f, size.height / 2f) {
                            val brush = Brush.sweepGradient(
                                colors = colors,
                                center = Offset(x = 0.5f, y = 0.5f)
                            )
                            drawPath(
                                path = morphPath,
                                brush = brush,
                                style = Stroke(6.dp.toPx(), cap = StrokeCap.Round)
                            )
                        }
                    }
                }
            }
    )
}

private val colors = listOf(
    Color(color = 0xFF3FCEBC),
    Color(color = 0xFF4EC2B2),
    Color(color = 0xFF31D38D),
    Color(color = 0xFFA72DE9),
    Color(color = 0xFF76119B),
    Color(color = 0xFF68CE6D),
    Color(color = 0xFF28C2E9),
    Color(color = 0xFFA51F9E),
    Color(color = 0xFF3BCABA),
)

fun Morph.toComposePath(process: Float, scale: Float = 1f, path: Path = Path()): Path {
    var first = true
    path.rewind()
    forEachCubic(progress = process) { bezier ->
        if (first) {
            path.moveTo(bezier.anchor0X * scale, bezier.anchor0Y * scale)
            first = false
        }
        path.cubicTo(
            bezier.control0X * scale, bezier.control1Y * scale,
            bezier.control1X * scale, bezier.control1Y * scale,
            bezier.anchor1X * scale, bezier.anchor1Y * scale
        )
    }
    path.close()
    return path
}